#!/usr/bin/python -S
"""
pyb_test.py: Tests for pyb.py
"""

__author__ = 'Andy Chu'


import sys
import pprint
import unittest

# TODO: Remove these deps, or copy testy into Poly, or something
from pan.core import json
from pan.test import testy

import pyb  # module under test
import raw_decode


class OfficialTest(testy.Test):

  DATA_RELATIVE_PATH = 'testdata/official'

  def setUpOnce(self):
    desc_set = self.data.String('unittest.desc.json-from-protoc')
    self.db = pyb.DescriptorSet.FromJson(desc_set)
    self.raw = self.data.Bytes('golden_message')

    self.TestAllTypes = self.db.Type('TestAllTypes')
    self.unittest = self.db.AllTypes()
    self.message = self.TestAllTypes(self.raw)

  def testDecodeGoldenBenchmark(self):
    self.TestAllTypes(self.raw)

  def testDecodeGoldenMessage(self):
    return  # TODO: empty [] fields
    expected = self.data.DecodeJson('json_format_unittest_data.txt')
    # NOTE: JSON bytes as generated by protoc appears to be base64-encoded, for
    # the sake of the browser.  Causes errors.  C-escaping would have been nicer
    # I think.
    self.verify.EqualAsJson(expected, self.message)

  def verifyField(self, name, value):
    self.verify.Equal(getattr(self.message, name), value)

  def testOptionalInts(self):
    # Why doesn't this work?
    self.verifyField('optional_int32', 101)
    self.verifyField('optional_int64', 102)
    self.verifyField('optional_uint32', 103)
    self.verifyField('optional_uint64', 104)
    self.verifyField('optional_sint32', 105)
    self.verifyField('optional_sint64', 106)

  def testFixed(self):
    self.verifyField('optional_sint64', 106)
    self.verifyField('optional_fixed32', 107)
    self.verifyField('optional_fixed64', 108)
    self.verifyField('optional_sfixed32', 109)
    self.verifyField('optional_sfixed64', 110)

  def testFloats(self):
    self.verifyField('optional_float', 111)
    self.verifyField('optional_double', 112)

  def testBool(self):
    self.verifyField('optional_bool', True)
    # Also check the type, since 1 == True
    value = self.message.optional_bool
    self.verify.Equal(type(value), bool)

  def testStrings(self):
    self.verifyField('optional_string', u'115')
    self.verifyField('optional_bytes', '116')

  def testOptionalGroup(self):
    self.verifyField('optionalgroup', pyb.RawMessage(a=117))

  def testEnums(self):
    self.verifyField('optional_nested_enum', self.TestAllTypes.BAZ)
    self.verifyField('optional_foreign_enum', self.unittest.FOREIGN_BAZ)
    # TODO: should be in another package
    self.verifyField('optional_import_enum', self.unittest.IMPORT_BAZ)

  def testRepeatedInts(self):
    self.verifyField('repeated_int32', [201, 301])
    self.verifyField('repeated_int64', [202, 302])

  def testPrint(self):
    return
    print json.dumps(self.message, indent=2, sort_keys=True)


class AddressBookTest(testy.Test):

  DATA_RELATIVE_PATH = 'testdata/addressbook'

  def setUpOnce(self):
    desc_set = self.data.String('addressbook.desc.json-from-protoc')
    self.db = pyb.DescriptorSet.FromJson(desc_set)
    self.raw = self.data.Bytes('addressbook.encoded')

  def testDecodeAsJson(self):
    message = self.db.Type('AddressBook')(self.raw)
    dictionary = pyb.ToDict(message)
    expected = self.data.DecodeJson('addressbook.json-from-protoc')

    # TODO: Message.ToJson -> takes into account enums!
    # Or maybe use JsonFormat like protobuf does
    self.verify.EqualAsJson(expected, dictionary, ignore_whitespace=True)

  def testDecodeMessage(self):
    message = self.db.Type('AddressBook')(self.raw)
    self.verify.Equal(message.person[0].email, 'andy@foo.com')
    self.verify.Equal(len(message.person), 2)


class TrivialTest(testy.Test):

  DATA_RELATIVE_PATH = 'testdata/trivial'

  def setUpOnce(self):
    self.desc_set = self.data.String('test.desc.json-from-pyb')
    self.db = pyb.DescriptorSet.FromJson(self.desc_set)
    self.raw = self.data.Bytes('test.encoded')

  def testDecodeDescriptor(self):
    desc_set = self.data.Bytes('test.desc.encoded')
    message = pyb.FileDescriptorSet(desc_set)
    d = pyb.ToDict(message)
    self.verify.EqualAsJson(
        d,
        { 'file': [
          { 'message_type': [
            { 'field': [{'label': 2, 'name': u'a', 'number': 1, 'type': 5}],
              'name': u'Test1'}
            ],
          'name': u'test.proto'
          }]
        })

    # TODO: I have decoded a descriptor here.  I should be able to a decode a
    # message directly from this type.  Need to bootstrap a bit more.

  def testDecodeRaw(self):
    message = raw_decode.DecodeWithoutDescriptor(self.raw)
    self.verify.EqualAsJson(message, {1: 150})

  def testDecode(self):
    root = self.db.AllTypes()
    print root.Test1
    # Test construction with a string
    message = root.Test1(self.raw)
    self.verify.Equal(message.a, 150)

    Test1 = self.db.Type('Test1')
    message = Test1(self.raw)
    print Test1
    self.verify.Equal(message.a, 150)
    


class JsonTest(testy.Test):

  def testEncodeJson(self):
    m = pyb.RawMessage()
    m.foo = 3
    m.bar = pyb.RawMessage()
    m.bar.baz = 'eggs'
    self.verify.LongStringsEqual(
        pyb.EncodeJson(m, indent=2),
        """\
{
  "foo": 3,
  "bar": {
    "baz": "eggs"
  }
}
""", ignore_whitespace=True)



class DescriptorSetTest(testy.Test):

  DATA_RELATIVE_PATH = 'testdata/addressbook'

  def setUpOnce(self):
    #self.desc_set = self.data.String('addressbook.desc.json-from-protoc')
    #self.db = pyb.DescriptorSet.FromJson(self.desc_set)

    self.desc_set = self.data.Bytes('addressbook.desc.encoded')
    self.db = pyb.DescriptorSet.FromBinary(self.desc_set)

    self.AddressBook = self.db.Type('AddressBook')
    self.Person = self.db.Type('Person')
    self.PhoneNumber = self.db.Type('Person.PhoneNumber')

  def testFromDescriptorSet(self):
    addressbook_pb = self.db.AllTypes()
    print addressbook_pb
    print addressbook_pb.AddressBook
    print addressbook_pb.Person
    print addressbook_pb.Person.PhoneNumber
    self.verify.Equal(addressbook_pb.Person.MOBILE, 0)
    self.verify.Equal(addressbook_pb.Person.HOME, 1)
    self.verify.Equal(addressbook_pb.Person.WORK, 2)

  def testMakeType(self):
    AddressBook = self.AddressBook
    Person = self.Person
    PhoneNumber = self.PhoneNumber

    n = PhoneNumber()
    # Test default value
    self.verify.Equal(n.type, 'HOME')
    try:
      n.foo = 1
    except AttributeError:
      pass
    else:
      # TODO: Fail doesn't exist in testy!
      self.verify.Fail('Setting unknown attribute should have failed')

    print pyb.ToDict(n)

    # This is fucking broken, it's looking at the default value.  Ugh.
    #self.verify.IsFalse(hasattr(n, 'type'))

    self.verify.IsFalse('type' in n)

    # Verify the default value
    self.verify.Equal(n.type, 'HOME')

    n.type = 'WORK'
    self.verify.Equal(n.type, 'WORK')
    self.verify.IsTrue('type' in n)
    del n.type
    self.verify.IsFalse('type' in n)

    print AddressBook
    book1 = AddressBook()

    p = Person(name='andy', id=10)
    self.verify.Equal(len(book1.person), 0)
    book1.person.append(p)
    self.verify.Equal(len(book1.person), 1)

    book2 = AddressBook()
    # BROKEN: Should start out empty, not use class attribute!!
    self.verify.Equal(len(book2.person), 0)
    book2.person.append(p)
    self.verify.Equal(book2.person[0], p)

    print pyb.ToDict(book2)

    m = pyb.RawMessage()
    print m
    m.person = 'andy'
    print m.person

    return

    # Can't just make a single type.  Need to make them recursively.
    # Person.PhoneNumber
    tutorial = dd.AllTypes(root='tutorial')
    root = dd.AllTypes()
    root.tutorial = 'Foo.bar'




if __name__ == '__main__':
  testy.RunThisModule()
